/*
 * Copyright 2023-2025, Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *   Haiku Development Team
 */

#include <boot/platform.h>
#include <boot/stage2.h>
#include <boot/stdio.h>

#include <arch_cpu_defs.h>

#include "efi_platform.h"
#include "generic_mmu.h"
#include "mmu.h"
#include "serial.h"

// 从entry.S导入
extern "C" void arch_enter_kernel(uint64 satp, addr_t kernelArgs,
        addr_t kernelEntry, addr_t kernelStackTop);

// 从arch_mmu.cpp导入
extern void arch_mmu_post_efi_setup(size_t memory_map_size,
    efi_memory_descriptor *memory_map, size_t descriptor_size,
    uint32_t descriptor_version);

extern uint64_t arch_mmu_generate_post_efi_page_tables(size_t memory_map_size,
    efi_memory_descriptor *memory_map, size_t descriptor_size,
    uint32_t descriptor_version);


void
arch_convert_kernel_args(void)
{
	fix_address(gKernelArgs.arch_args.fdt);
}


void
arch_start_kernel(addr_t kernelEntry)
{
	// 为内核参数分配虚拟内存
	struct kernel_args *kernelArgs = NULL;
	if (platform_allocate_region((void **)&kernelArgs,
			sizeof(struct kernel_args), 0, false) != B_OK)
		panic("无法分配内核参数内存");

	addr_t virtKernelArgs;
	platform_bootloader_address_to_kernel_address((void*)kernelArgs,
		&virtKernelArgs);

	// 设置平台类型为龙芯平台
	gKernelArgs.arch_args.machine_platform = kPlatformLoongson;

	// 准备退出EFI引导服务
	// 读取内存映射
	// 第一次调用是为了确定缓冲区大小
	size_t memory_map_size = 0;
	efi_memory_descriptor dummy;
	efi_memory_descriptor *memory_map;
	size_t map_key;
	size_t descriptor_size;
	uint32_t descriptor_version;
	if (kBootServices->GetMemoryMap(&memory_map_size, &dummy, &map_key,
		&descriptor_size, &descriptor_version) != EFI_BUFFER_TOO_SMALL) {
		panic("无法确定系统内存映射大小");
	}

	// 分配一个两倍大小的缓冲区，以防在调用ExitBootServices之间变大
	size_t actual_memory_map_size = memory_map_size * 2;
	memory_map
		= (efi_memory_descriptor *)kernel_args_malloc(actual_memory_map_size);

	if (memory_map == NULL)
		panic("无法分配内存映射缓冲区");

	// 读取（并打印）内存映射
	memory_map_size = actual_memory_map_size;
	if (kBootServices->GetMemoryMap(&memory_map_size, memory_map, &map_key,
		&descriptor_size, &descriptor_version) != EFI_SUCCESS) {
		panic("无法获取系统内存映射");
	}

	addr_t addr = (addr_t)memory_map;
	dprintf("系统提供的内存映射:\n");
	for (size_t i = 0; i < memory_map_size / descriptor_size; ++i) {
		efi_memory_descriptor *entry
			= (efi_memory_descriptor *)(addr + i * descriptor_size);
		dprintf("  物理地址: 0x%08" PRIx64 "-0x%08" PRIx64
			", 虚拟地址: 0x%08" PRIx64 "-0x%08" PRIx64
			", 类型: %s (%#x), 属性: %#" PRIx64 "\n",
			entry->PhysicalStart,
			entry->PhysicalStart + entry->NumberOfPages * B_PAGE_SIZE,
			entry->VirtualStart,
			entry->VirtualStart + entry->NumberOfPages * B_PAGE_SIZE,
			memory_region_type_str(entry->Type), entry->Type,
			entry->Attribute);
	}

	// 生成ExitBootServices后使用的页表
	uint64_t pgd = arch_mmu_generate_post_efi_page_tables(
		memory_map_size, memory_map, descriptor_size, descriptor_version);
	dprintf("页表基址: 0x%016" B_PRIx64 "\n", pgd);

	// 尝试获取内存映射并退出引导服务
	// 这需要在循环中完成，因为ExitBootServices可能会改变内存映射
	// 更好的是：在第一次调用ExitBootServices之后，只能调用GetMemoryMap和ExitBootServices，
	// 因为固件被允许部分退出。这就是为什么为内存映射分配两倍空间的原因，
	// 因为现在不可能分配更多空间了。
	// 变化的内存映射不应该影响生成的页表，因为它们只需要知道最大地址，而不是任何特定条目。

	dprintf("调用ExitBootServices。再见，EFI！\n");
	serial_disable();
	while (true) {
		if (kBootServices->ExitBootServices(kImage, map_key) == EFI_SUCCESS) {
			// 断开与EFI serial_io / stdio服务的连接
			serial_kernel_handoff();
			dprintf("已从EFI串行服务断开连接\n");
			break;
		}

		memory_map_size = actual_memory_map_size;
		if (kBootServices->GetMemoryMap(&memory_map_size, memory_map, &map_key,
				&descriptor_size, &descriptor_version) != EFI_SUCCESS) {
			panic("无法获取系统内存映射");
		}
	}

	// 更新EFI，生成最终的内核物理内存映射等
	arch_mmu_post_efi_setup(memory_map_size, memory_map,
			descriptor_size, descriptor_version);

	// 在可怕的后EFI环境中重新初始化和激活串行。小丑自由地漫游大地。
	serial_init();
	serial_enable();

	// 复制最终的内核参数
	// 这应该是跳转到内核之前的最后一步
	// 因为即使在最后一刻也会对kernel_args进行一些修复
	memcpy(kernelArgs, &gKernelArgs, sizeof(struct kernel_args));

	// 启动其他CPU
	arch_smp_boot_other_cpus(kernelArgs, kernelEntry);

	// 进入内核！
	dprintf("arch_enter_kernel(pgd: %#" B_PRIxADDR ", kernelArgs: %#" B_PRIxADDR
		", kernelEntry: %#" B_PRIxADDR ", sp: %#" B_PRIxADDR ")\n",	pgd,
		virtKernelArgs, kernelEntry, kernelArgs->cpu_kstack[0].start
			+ kernelArgs->cpu_kstack[0].size);

	arch_enter_kernel(pgd, virtKernelArgs, kernelEntry,
		kernelArgs->cpu_kstack[0].start + kernelArgs->cpu_kstack[0].size);
}